package com.xiaoxi.bms.common.provider;

import com.xiaoxi.bms.common.constant.constant.UserCommonConstants;
import com.xiaoxi.bms.common.constant.constant.TimeConstants;
import com.xiaoxi.bms.common.constant.enumeration.statuscode.user.LoginUserEnums;
import com.xiaoxi.bms.common.exception.BMSSecurityException;
import com.xiaoxi.bms.common.util.RedisUtils;
import com.xiaoxi.bms.mapper.user.BMSUserMapper;
import com.xiaoxi.bms.domain.user.BMSUser;
import com.xiaoxi.bms.service.user.impl.BMSUserServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @Author xiaoxi
 * @ProjectName bms
 * @PackageName com.xiaoxi.bms.common.provider
 * @ClassName BMSSecurityAuthenticationProvider
 * @Description TODO
 * @Date 2023/6/5 12:23
 * @Version 1.0
 */
@Component
@Slf4j
public class BMSSecurityAuthenticationProvider implements AuthenticationProvider {

    @Resource
    private BMSUserServiceImpl bmsUserService;

    @Resource
    private RedisUtils redisUtil;

    @Resource
    private BMSUserMapper bmsUserMapper;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Value("${bms.login.bad-password-attempts}")
    private String count;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String userUsername = (String) authentication.getPrincipal();
        String userPassword = (String) authentication.getCredentials();

        BMSUser bmsUser = bmsUserService.loadUserByUsername(userUsername);
        String sqlPassword = bmsUser.getPassword();


        // 比对密码
        if (!passwordEncoder.matches(userPassword, sqlPassword)) {

            // 当密码错误，用户有五次机会可试密码
            Integer num = (Integer) redisUtil.get(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);

            if (num != null) {

                Long expireTime = redisUtil.getKeyExpire(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);

                // 第N次登陆失败，超过count次被锁定
                if (num > Integer.parseInt(count)) {

                    updateUserAccountNonLocked(bmsUser);
                    redisUtil.remove(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);
                    throw new BMSSecurityException(LoginUserEnums.ACCOUNT_LOCKED);

                } else {        // 低于 count 次 友善提示 错误次数

                    redisUtil.set(UserCommonConstants.FAIL_LOGIN_NUM + userUsername, ++num, expireTime);
                    throw new BMSSecurityException(LoginUserEnums.PASSWORD_MISTAKE.getCode()
                            , LoginUserEnums.PASSWORD_MISTAKE.getMessage() + "再错 " + (Integer.parseInt(count) + 1 - num) + " 次将在：" + expireTime + " 秒锁定");

                }

            } else {

                // 第一次登陆失败
                redisUtil.set(UserCommonConstants.FAIL_LOGIN_NUM + userUsername, 1, TimeConstants.REFRESH_LOGIN_TIME);
                throw new BMSSecurityException(LoginUserEnums.PASSWORD_MISTAKE);

            }

        }

        // 密码验证正确
        redisUtil.remove(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);
        return new UsernamePasswordAuthenticationToken(bmsUser, "", bmsUser.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

    /**
     * 更新 user 表。。多次登录失败，被锁定
     *
     * @param bmsUser 当前登陆
     */
    private void updateUserAccountNonLocked(BMSUser bmsUser) {
        bmsUser.setAccountNonLocked(true);
        int num = bmsUserMapper.updateById(bmsUser);
        if (num == 0) {
            throw new BMSSecurityException(LoginUserEnums.PLEASE_REFRESH_AND_RETRY);
        }
    }

}

